package net.xinshi.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.xinshi.boot.AppConfig;
import net.xinshi.commons.HttpUtil;
import net.xinshi.domain.ApiRequest;
import net.xinshi.domain.JsonResponseObject;
import net.xinshi.exception.ApiException;
import net.xinshi.model.ApiLogInputDTO;
import net.xinshi.model.ApiOutputDTO;
import net.xinshi.service.business.IApiLogService;
import net.xinshi.service.business.IApiService;
import net.xinshi.utils.DateUtils;
import net.xinshi.utils.ErrorCode;
import net.xinshi.utils.MD5SignUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;


/**
 * @author <a href="mailto:liukuo@xinshi.net">liukuo</a>
 * @version 1.0
 * @description API入口
 * @since 2017/6/27.
 */
@RestController
@RequestMapping("/api/")
public class ApiController extends AbstractRestController {
    private static final Logger LOGGER = LogManager.getLogger(ApiController.class);

    @Autowired
    private IApiService apiService;
    @Autowired
    private IApiLogService apiLogService;
    @Autowired
    private AppConfig appConfig;
    @Value("${spring.profiles.active}")
    private String evn;

    @RequestMapping(value = "/rest", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public JsonResponseObject rest(@RequestBody ApiRequest req, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.info("请求rest接口,请求参数是:" + JSON.toJSONString(req));
        long beginTime = System.currentTimeMillis();
        ApiOutputDTO api = null;
        String res = null;
        long providerCostTime = 0;
        long totalCostTime = 0;
        Cookie[] cookies = request.getCookies();
        LOGGER.info("请求cookie是:" + JSON.toJSONString(cookies));
        try {
            //检查请求参数是否正确
            checkParam(req);
            //如果是生产环境才需要检查签名
            if (StringUtils.equalsIgnoreCase(appConfig.getValidateSign(), "Y")) {
                //检查md5签名
                checkSign(req);
            }

            String apiId = req.getApiId();
            Map<String, ApiOutputDTO> apiMap = apiService.getApiMap();
            api = apiMap.get(apiId);
            LOGGER.info("根据apiId:{},查询api,查询结果是:{}", apiId, JSON.toJSONString(api));
            if (api == null) {
                throw new ApiException(ErrorCode.ERR_API_ID_WRONG, ErrorCode.ERR_API_ID_WRONG.getMsg());
            }
            String url = api.getApiUrl();
            if (StringUtils.isBlank(url)) {
                throw new ApiException(ErrorCode.ERR_API_URL_WRONG, ErrorCode.ERR_API_URL_WRONG.getMsg());
            }
            if (!StringUtils.startsWith(url, "http")) {
                url = appConfig.getUrlDomain() + url;
            }
            if (api.getApiState() == 1) {
                throw new ApiException(ErrorCode.ERR_API_STOP_USE, ErrorCode.ERR_API_STOP_USE.getMsg());
            }
            String requestData = req.getRequestData();
            if (StringUtils.isBlank(requestData)) {
                throw new ApiException(ErrorCode.ERR_REQUEST_DATA_IS_REQUIRE, ErrorCode.ERR_REQUEST_DATA_IS_REQUIRE.getMsg());
            }
            JSONObject jRequestData = JSON.parseObject(requestData);
            if (jRequestData == null) {
                throw new ApiException(ErrorCode.ERR_REQUEST_DATA_FORMAT_ERROR, ErrorCode.ERR_REQUEST_DATA_FORMAT_ERROR.getMsg());
            }
            long providerTime = System.currentTimeMillis();
            res = HttpUtil.post(url, jRequestData, cookies, request, response);
            providerCostTime = System.currentTimeMillis() - providerTime;
            LOGGER.info("调用Api,返回结果是:" + res);
            if (StringUtils.isBlank(res)) {
                throw new ApiException(ErrorCode.ERR_RESPONSE_EMPTY, ErrorCode.ERR_RESPONSE_EMPTY.getMsg());
            }
            JSONObject jResponse = null;
            try {
                jResponse = JSON.parseObject(res);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ApiException(ErrorCode.ERR_RESPONSE_FORMAT_ERROR, ErrorCode.ERR_RESPONSE_FORMAT_ERROR.getMsg());
            }
            totalCostTime = System.currentTimeMillis() - beginTime;

            JsonResponseObject ret = new JsonResponseObject();
            ret.setCode(jResponse.getString("code"));
            ret.setMsg(jResponse.getString("msg"));
            ret.setData(jResponse.get("data"));
            ret.setTimestamp(new Date());
            return ret;
        } catch (Exception e) {
            LOGGER.error("请求API接口，code: {}, msg: {}", ErrorCode.ERR_UNKNOWN_ERROR.getCode(), e.getMessage());
            if (e instanceof ApiException) {
                ApiException ex = (ApiException) e;
                return new JsonResponseObject(ex.getCode(), ex.getMessage());
            }
            return new JsonResponseObject(ErrorCode.ERR_UNKNOWN_ERROR.getCode(), ErrorCode.ERR_UNKNOWN_ERROR.getMsg());
        } finally {
            addApiLog(req, api, res, providerCostTime, totalCostTime);//保存日志
        }
    }

    @RequestMapping(value = "/rest", method = RequestMethod.GET)
    @ResponseBody
    public JsonResponseObject restGet() {
        JsonResponseObject res = new JsonResponseObject();
        res.setCode(ErrorCode.ERR_METHOD.getCode());
        res.setMsg("请使用POST方式调用接口");
        res.setTimestamp(new Date());
        return res;
    }

    private void checkParam(ApiRequest req) {
        if (StringUtils.isBlank(req.getApiId())) {
            throw new ApiException(ErrorCode.ERR_API_IS_REQUIRE, ErrorCode.ERR_API_IS_REQUIRE.getMsg());
        }
        if (StringUtils.isBlank(req.getTimestamp())) {
            throw new ApiException(ErrorCode.ERR_TIME_STAMP_IS_REQUIRE, ErrorCode.ERR_TIME_STAMP_IS_REQUIRE.getMsg());
        }
        if (StringUtils.isBlank(req.getSign())) {
            throw new ApiException(ErrorCode.ERR_SIGN_IS_REQUIRE, ErrorCode.ERR_SIGN_IS_REQUIRE.getMsg());
        }
        if (StringUtils.isBlank(req.getAppToken())) {
            throw new ApiException(ErrorCode.ERR_APP_TOKEN_IS_REQUIRE, ErrorCode.ERR_APP_TOKEN_IS_REQUIRE.getMsg());
        }
        if (appConfig.getAppTokens() != null && !appConfig.getAppTokens().contains(req.getAppToken())) {
            throw new ApiException(ErrorCode.ERR_APP_TOKEN_ERROR, ErrorCode.ERR_APP_TOKEN_ERROR.getMsg());
        }
        JsonNode originRequest = req.getOriginRequest();
        if (originRequest == null) {
            throw new ApiException(ErrorCode.ERR_REQUEST_PARAM_IS_REQUIRE, ErrorCode.ERR_REQUEST_PARAM_IS_REQUIRE.getMsg());
        }

        String timestamp = req.getTimestamp();
        Date time = null;
        try {
            time = DateUtils.parseAuto(timestamp);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ApiException(ErrorCode.ERR_TIME_STAMP_FORMAT_ERROR, ErrorCode.ERR_TIME_STAMP_FORMAT_ERROR.getMsg());
        }
        /*if (System.currentTimeMillis() - time.getTime() > 5 * 60 * 1000) {
            throw new ApiException(ErrorCode.ERR_TIME_STAMP_TIME_OUT_IS_REQUIRE, ErrorCode.ERR_TIME_STAMP_TIME_OUT_IS_REQUIRE.getMsg());
        }*/
        JSONObject jsonObject = null;
        try {
            jsonObject = JSON.parseObject(originRequest.toString());
        } catch (Exception e) {
            e.printStackTrace();
            throw new ApiException(ErrorCode.ERR_REQUEST_PARAM_FORMAT_ERROR, ErrorCode.ERR_REQUEST_PARAM_FORMAT_ERROR.getMsg());
        }
        try {
            JSONObject attrs = jsonObject.getJSONObject("REQUEST_ATTRS");
            if (attrs == null) {
                throw new ApiException(ErrorCode.ERR_REQUEST_ATTR_IS_REQUIRE, ErrorCode.ERR_REQUEST_ATTR_IS_REQUIRE.getMsg());
            }
        } catch (ApiException e) {
            e.printStackTrace();
            throw new ApiException(ErrorCode.ERR_REQUEST_ATTR_FORMAT_ERROR, ErrorCode.ERR_REQUEST_ATTR_FORMAT_ERROR.getMsg());
        }
        try {
            JSONObject reqData = jsonObject.getJSONObject("REQUEST_DATA");
            if (reqData == null) {
                throw new ApiException(ErrorCode.ERR_REQUEST_DATA_IS_REQUIRE, ErrorCode.ERR_REQUEST_DATA_IS_REQUIRE.getMsg());
            }
        } catch (ApiException e) {
            e.printStackTrace();
            throw new ApiException(ErrorCode.ERR_REQUEST_DATA_FORMAT_ERROR, ErrorCode.ERR_REQUEST_DATA_FORMAT_ERROR.getMsg());
        }
    }

    /**
     * 保存调用日志
     *
     * @param req              请求报文
     * @param apiOutputDTO     api信息
     * @param response         响应报文
     * @param providerCostTime 接口调用时间
     * @param totalCostTime    接口总共花费时间
     */
    private void addApiLog(ApiRequest req, ApiOutputDTO apiOutputDTO, String response, long providerCostTime, long totalCostTime) {
        if (req == null || apiOutputDTO == null) {
            return;
        }
        ApiLogInputDTO apiLog = new ApiLogInputDTO();
        apiLog.setApiId(apiOutputDTO.getApiId());
        apiLog.setApiName(apiOutputDTO.getApiName());
        apiLog.setApiUrl(appConfig.getUrlDomain() + apiOutputDTO.getApiUrl());
        apiLog.setCallTime(new Date());
        apiLog.setTotalCostTime((int) (totalCostTime == 0 ? providerCostTime : totalCostTime));
        apiLog.setProviderCostTime((int) providerCostTime);
        apiLog.setRequestData(req.getOriginRequest().toString());
        apiLog.setResponseData(response);
        apiLog.setSerialNo(req.getTransactionUuid());
        try {
            JSONObject jResponse = JSON.parseObject(response);
            apiLog.setResponseCode(jResponse.getString("code"));
        } catch (Exception e) {
        }
        apiLogService.addLog(apiLog);
        LOGGER.info("调用Api,增加调用记录:" + JSON.toJSONString(apiLog));
    }

    /**
     * 检查签名是否正确
     *
     * @param req
     */
    private void checkSign(ApiRequest req) {
        if (req == null) {
            return;
        }
        JsonNode originRequest = req.getOriginRequest();
        JSONObject jsonObject = JSON.parseObject(originRequest.toString());
        JSONObject attrs = jsonObject.getJSONObject("REQUEST_ATTRS");
        JSONObject reqData = jsonObject.getJSONObject("REQUEST_DATA");
        String reqSign = attrs.getString("Sign");//请求参数里的签名
        attrs.remove("Sign");
        attrs.put("REQUEST_DATA", reqData);
        List<String> keys = new ArrayList<String>(attrs.keySet());
        //排序
        Collections.sort(keys);
        StringBuilder sortMsg = new StringBuilder();
        //拼接字符串
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = attrs.get(key) + "";
            sortMsg.append(key).append("=").append(value).append("&");
        }
        sortMsg.append(appConfig.getSign());

        //验证签名
        boolean isRight = MD5SignUtils.verify(sortMsg.toString(), reqSign, "UTF-8");
        if (!isRight) {
            throw new ApiException(ErrorCode.ERR_SIGN_ERROR, ErrorCode.ERR_SIGN_ERROR.getMsg());
        }
    }
}
